/********************************************************************
 *                                                                  *
 * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE.   *
 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS     *
 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING.       *
 *                                                                  *
 * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002             *
 * by the Xiph.Org Foundation http://www.xiph.org/                  *
 *                                                                  *
 ********************************************************************/

package org.xiph.libvorbis;

import java.util.*;

import org.xiph.libogg.*;
import static org.xiph.libvorbis.vorbis_constants.integer_constants.*;

public class vorbis_dsp_state {

	int analysisp;

	public vorbis_info vi;

	float[][] pcm; // float **pcm // float **pcmret

	int pcm_storage;
	public int pcm_current;
	int pcm_returned;

	int preextrapolate;
	int eofflag;

	int lW;
	int W;
	int nW;
	int centerW;

	int granulepos;
	int sequence;

	int glue_bits;
	int time_bits;
	int floor_bits;
	int res_bits;

	private_state backend_state;

	public vorbis_dsp_state() {
	}

	private vorbis_look_floor1 floor1_look(vorbis_info_floor1 info) {

		int sortpointer[] = new int[VIF_POSIT + 2];
		vorbis_look_floor1 look = new vorbis_look_floor1();

		int i, j, n = 0;

		look.vi = info;
		look.n = info.postlist[1];

		// we drop each position value in-between already decoded values,
		// and use linear interpolation to predict each new value past the
		// edges. The positions are read in the order of the position
		// list... we precompute the bounding positions in the lookup. Of
		// course, the neighbors can change (if a position is declined), but
		// this is an initial mapping

		for (i = 0; i < info.partitions; i++)
			n += info.class_dim[info.partitionclass[i]];
		n += 2;
		look.posts = n;

		// also store a sorted position index
		for (i = 0; i < n; i++)
			sortpointer[i] = info.postlist[i];
		// sortpointer[i] = info.postlist+i;

		// static int icomp(const void *a,const void *b) { return(**(int
		// **)a-**(int **)b); }
		// qsort( sortpointer, n, sizeof(*sortpointer), icomp );

		Arrays.sort(sortpointer, 0, n);

		// points from sort order back to range number
		for (i = 0; i < n; i++)
			for (j = 0; j < n; j++)
				if (sortpointer[i] == info.postlist[j])
					look.forward_index[i] = j;
		// look.forward_index[i] = sortpointer[i]-info.postlist;

		// points from range order to sorted position
		for (i = 0; i < n; i++)
			look.reverse_index[look.forward_index[i]] = i;

		// we actually need the post values too
		for (i = 0; i < n; i++)
			look.sorted_index[i] = info.postlist[look.forward_index[i]];

		switch (info.mult) { // quantize values to multiplier spec

		case 1:
			look.quant_q = 256;
			break;
		case 2:
			look.quant_q = 128;
			break;
		case 3:
			look.quant_q = 86;
			break;
		case 4:
			look.quant_q = 64;
			break;
		}

		// discover our neighbors for decode where we don't use fit flags (that
		// would push the neighbors outward)
		for (i = 0; i < n - 2; i++) {
			int lo = 0;
			int hi = 1;
			int lx = 0;
			int hx = look.n;
			int currentx = info.postlist[i + 2];
			for (j = 0; j < i + 2; j++) {
				int x = info.postlist[j];
				if (x > lx && x < currentx) {
					lo = j;
					lx = x;
				}
				if (x < hx && x > currentx) {
					hi = j;
					hx = x;
				}
			}
			look.loneighbor[i] = lo;
			look.hineighbor[i] = hi;
		}

		return look;
	}

	private vorbis_look_residue0 res0_look(vorbis_info_residue0 info) {

		vorbis_look_residue0 look = new vorbis_look_residue0();
		codec_setup_info ci = vi.codec_setup;

		int j, k, acc = 0;
		int dim;
		int maxstage = 0;

		look.info = info;

		look.parts = info.partitions;
		look.fullbooks = ci.fullbooks;
		// PTR CITY

		// look.phrasebook = ci.fullbooks+info.groupbook;
		look.phrasebook = ci.fullbooks[info.groupbook];
		dim = look.phrasebook.dim;

		// look.partbooks = _ogg_calloc(look.parts,sizeof(*look.partbooks));
		look.partbooks = new codebook[look.parts][];

		for (j = 0; j < look.parts; j++) {

			int stages = ilog(info.secondstages[j]);
			if (stages > 0) {
				if (stages > maxstage)
					maxstage = stages;

				// look.partbooks[j]=_ogg_calloc(stages,sizeof(*look.partbooks[j]));
				look.partbooks[j] = new codebook[stages];

				for (k = 0; k < stages; k++)

					if ((info.secondstages[j] & (1 << k)) > 0) {

						// look.partbooks[j][k] = ci.fullbooks +
						// info.booklist[acc++];

						look.partbooks[j][k] = ci.fullbooks[info.booklist[acc++]];

						// #ifdef TRAIN_RES
						// look.training_data[k][j]=calloc(look.partbooks[j][k].entries,sizeof(***look.training_data));
						// #endif
					}
			}
		}

		look.partvals = new Double(Math.rint(Math.pow(look.parts, dim))).intValue();
		look.stages = maxstage;

		// look.decodemap=_ogg_malloc(look.partvals*sizeof(*look.decodemap));
		look.decodemap = new int[look.partvals][dim];

		for (j = 0; j < look.partvals; j++) {
			int val = j;
			int mult = look.partvals / look.parts;

			// look.decodemap[j]=_ogg_malloc(dim*sizeof(*look.decodemap[j]));

			for (k = 0; k < dim; k++) {
				int deco = val / mult;
				val -= deco * mult;
				mult /= look.parts;
				look.decodemap[j][k] = deco;
			}
		}

		// #ifdef TRAIN_RES
		// static int train_seq=0;
		// look.train_seq=train_seq++;
		// #endif

		return look;
	}

	private boolean _vds_shared_init(boolean encp) {

		int i;
		codec_setup_info ci = vi.codec_setup;
		int hs;

		if (ci == null)
			return false;

		hs = ci.halfrate_flag;

		backend_state = new private_state();

		backend_state.modebits = ilog2(ci.modes);

		backend_state.transform[0][0].mdct_init(ci.blocksizes[0] >>> hs);
		backend_state.transform[1][0].mdct_init(ci.blocksizes[1] >>> hs);

		// Vorbis I uses only window type 0
		backend_state.window[0] = ilog2(ci.blocksizes[0]) - 6;
		backend_state.window[1] = ilog2(ci.blocksizes[1]) - 6;

		if (encp) { // encode/decode differ here

			// analysis always needs an fft
			backend_state.fft_look[0].drft_init(ci.blocksizes[0]);
			backend_state.fft_look[1].drft_init(ci.blocksizes[1]);

			// finish the codebooks
			if (ci.fullbooks == null) {

				// ci.fullbooks=_ogg_calloc(ci.books,sizeof(*ci.fullbooks));
				ci.fullbooks = new codebook[ci.books];

				for (i = 0; i < ci.books; i++) {
					ci.fullbooks[i] = new codebook();
					ci.fullbooks[i].vorbis_book_init_encode(ci.book_param[i]);
				}
			}

			// backend_state.psy=_ogg_calloc(ci.psys,sizeof(*backend_state.psy));
			backend_state.psy = new vorbis_look_psy[ci.psys];

			for (i = 0; i < ci.psys; i++) {
				backend_state.psy[i] = new vorbis_look_psy();
				backend_state.psy[i]._vp_psy_init(ci.psy_param[i], ci.psy_g_param, ci.blocksizes[ci.psy_param[i].blockflag] / 2, vi.rate);
			}

			analysisp = 1;
		} else {
			// decode the codebooks - not yet implemented
			System.out.println("_vds_shared_init DECODE not yet implemented");
			return false;
		}

		pcm_storage = ci.blocksizes[1];

		pcm = new float[vi.channels][pcm_storage];
		// pcmret = new float[ vi.channels ];

		lW = 0; // previous window size
		W = 0; // current window size

		// all vector indexes
		centerW = ci.blocksizes[1] / 2;

		pcm_current = centerW;

		// flr - will always be floor1
		// residue - look data will always fit in res0

		// initialize all the backend lookups
		// backend_state.flr =
		// _ogg_calloc(ci.floors,sizeof(*backend_state.flr));
		// backend_state.residue =
		// _ogg_calloc(ci.residues,sizeof(*backend_state.residue));

		backend_state.flr = new vorbis_look_floor1[ci.floors];
		backend_state.residue = new vorbis_look_residue0[ci.residues];

		// for ( i=0; i < ci.floors; i++ )
		// backend_state.flr[i] = _floor_P[ci.floor_type[i]].look( v,
		// ci.floor_param[i] );

		for (i = 0; i < ci.floors; i++) {
			backend_state.flr[i] = floor1_look(ci.floor_param[i]);
		}

		// for ( i=0; i < ci.residues; i++ )
		// backend_state.residue[i] = _residue_P[ci.residue_type[i]].look( v,
		// ci.residue_param[i] );

		for (i = 0; i < ci.residues; i++) {
			backend_state.residue[i] = res0_look(ci.residue_param[i]);
		}

		return true;
	}

	public boolean vorbis_analysis_init(vorbis_info _vi) {

		vi = _vi;

		if (!_vds_shared_init(true))
			return false;

		backend_state.psy_g_look = new vorbis_look_psy_global(vi);

		backend_state.ve = new envelope_lookup(vi);

		backend_state.bms = new bitrate_manager_state(vi);

		// compressed audio packets start after the headers with sequence number
		// 3
		sequence = 3;

		return true;
	}

	public boolean vorbis_analysis_headerout(vorbis_comment vc, ogg_packet op, ogg_packet op_comm, ogg_packet op_code) {

		oggpack_buffer opb;

		// first header packet **********************************************
		opb = new oggpack_buffer();
		if (!opb._vorbis_pack_info(vi))
			return false;

		// build the packet
		backend_state.header = new byte[opb.oggpack_bytes()];
		// memcpy(b->header,opb.buffer,oggpack_bytes(&opb));
		System.arraycopy(opb.buffer, 0, backend_state.header, 0, opb.oggpack_bytes());
		op.packet = backend_state.header;
		op.bytes = opb.oggpack_bytes();
		op.b_o_s = 1;
		op.e_o_s = 0;
		op.granulepos = 0;
		op.packetno = 0;

		// second header packet (comments) **********************************
		opb = new oggpack_buffer();
		if (!opb._vorbis_pack_comment(vc))
			return false;

		// build the packet
		backend_state.header1 = new byte[opb.oggpack_bytes()];
		// memcpy(b->header1,opb.buffer,oggpack_bytes(&opb));
		System.arraycopy(opb.buffer, 0, backend_state.header1, 0, opb.oggpack_bytes());
		op_comm.packet = backend_state.header1;
		op_comm.bytes = opb.oggpack_bytes();
		op_comm.b_o_s = 0;
		op_comm.e_o_s = 0;
		op_comm.granulepos = 0;
		op_comm.packetno = 1;

		// third header packet (modes/codebooks) ****************************
		opb = new oggpack_buffer();
		if (!opb._vorbis_pack_books(vi))
			return false;

		// build the packet
		backend_state.header2 = new byte[opb.oggpack_bytes()];
		// memcpy(b->header2,opb.buffer,oggpack_bytes(&opb));
		System.arraycopy(opb.buffer, 0, backend_state.header2, 0, opb.oggpack_bytes());
		op_code.packet = backend_state.header2;
		op_code.bytes = opb.oggpack_bytes();
		op_code.b_o_s = 0;
		op_code.e_o_s = 0;
		op_code.granulepos = 0;
		op_code.packetno = 2;

		return true;
	}

	public float[][] vorbis_analysis_buffer(int vals) {

		int i;

		// free header, header1, header2
		backend_state.header = null;
		backend_state.header1 = null;
		backend_state.header2 = null;

		// Do we have enough storage space for the requested buffer? If not,
		// expand the PCM (and envelope) storage

		if (pcm_current + vals >= pcm_storage) {

			pcm_storage = pcm_current + vals * 2;

			for (i = 0; i < vi.channels; i++) {
				pcm[i] = Arrays.copyOf(pcm[i], pcm_storage);
			}

		}

		return pcm;
	}

	public boolean vorbis_analysis_wrote(int vals) {

		codec_setup_info ci = vi.codec_setup;

		if (vals <= 0) {

			int order = 32;
			int i;
			float[] lpc = new float[order];

			// if it wasn't done earlier (very short sample)
			if (preextrapolate <= 0)
				_preextrapolate_helper();

			// We're encoding the end of the stream. Just make sure we have
			// [at least] a few full blocks of zeroes at the end.

			// Actually, we don't want zeroes; that could drop a large
			// amplitude off a cliff, creating spread spectrum noise that will
			// suck to encode. Extrapolate for the sake of cleanliness.

			vorbis_analysis_buffer(ci.blocksizes[1] * 3);
			eofflag = pcm_current;
			pcm_current += ci.blocksizes[1] * 3;

			for (i = 0; i < vi.channels; i++) {

				if (eofflag > order * 2) {
					// extrapolate with LPC to fill in
					int n;

					// make a predictor filter
					n = eofflag;
					if (n > ci.blocksizes[1])
						n = ci.blocksizes[1];

					// vorbis_lpc_from_data(v->pcm[i]+v->eofflag-n,lpc,n,order);
					vorbis_lpc_from_data(pcm[i], eofflag - n, lpc, n, order);

					// run the predictor filter
					// vorbis_lpc_predict(lpc,v->pcm[i]+v->eofflag-order,order,
					// v->pcm[i]+v->eofflag,v->pcm_current-v->eofflag);
					vorbis_lpc_predict(lpc, pcm[i], eofflag - order, order, eofflag, pcm_current - eofflag);

				} else {

					// not enough data to extrapolate (unlikely to happen due to
					// guarding the overlap, but bulletproof in case that
					// assumtion goes away). zeroes will do.

					// memset(v->pcm[i]+v->eofflag,0,(v->pcm_current-v->eofflag)*sizeof(*v->pcm[i]));

					// for ( int j=eofflag; j < pcm_current; j++ ) {
					// pcm[i][j] = 0;
					// }

					Arrays.fill(pcm[i], eofflag, pcm_current, 0);
				}
			}
		} else {

			if (pcm_current + vals > pcm_storage)
				return false;

			pcm_current += vals;

			// we may want to reverse extrapolate the beginning of a stream
			// too... in case we're beginning on a cliff!
			// clumsy, but simple. It only runs once, so simple is good.

			if ((preextrapolate <= 0) && pcm_current - centerW > ci.blocksizes[1])
				_preextrapolate_helper();
		}
		return true;
	}

	public boolean vorbis_bitrate_flushpacket(ogg_packet op) {

		private_state b = backend_state;
		bitrate_manager_state bm = b.bms;
		vorbis_block vb = bm.vb;
		int choice = PACKETBLOBS / 2;

		if (vb == null)
			return false;

		if (op != null) {

			vorbis_block_internal vbi = vb.internal;

			if (vb.vorbis_bitrate_managed())
				choice = bm.choice;

			op.packet = vbi.packetblob[choice].buffer; // oggpack_get_buffer();
			op.bytes = vbi.packetblob[choice].oggpack_bytes();
			op.b_o_s = 0;
			op.e_o_s = vb.eofflag;
			op.granulepos = vb.granulepos;
			op.packetno = vb.sequence; // for sake of completeness
		}

		// bm.vb = 0;
		bm.vb = null;
		return true;
	}

	public void _preextrapolate_helper() {

		int i;
		int order = 32;
		float[] lpc = new float[order];
		float[] work = new float[pcm_current];
		int j;
		preextrapolate = 1;

		if (pcm_current - centerW > order * 2) { // safety

			for (i = 0; i < vi.channels; i++) {

				// need to run the extrapolation in reverse!

				for (j = 0; j < pcm_current; j++)
					work[j] = pcm[i][pcm_current - j - 1];

				// prime as above
				vorbis_lpc_from_data(work, 0, lpc, pcm_current - centerW, order);

				// run the predictor filter
				vorbis_lpc_predict(lpc, work, pcm_current - centerW - order, order, pcm_current - centerW, centerW);

				for (j = 0; j < pcm_current; j++)
					pcm[i][pcm_current - j - 1] = work[j];
			}
		}
	}

	public float vorbis_lpc_from_data(float[] data, int offset1, float[] lpci, int n, int m) {

		// double *aut=alloca(sizeof(*aut)*(m+1));
		double[] aut = new double[m + 1];
		// double *lpc=alloca(sizeof(*lpc)*(m));
		double[] lpc = new double[m];
		double error;
		int i, j;

		// autocorrelation, p+1 lag coefficients
		j = m + 1;
		while (j-- > 0) {
			double d = 0; // double needed for accumulator depth
			for (i = j; i < n; i++)
				d += (double) data[offset1 + i] * data[offset1 + i - j];
			aut[j] = d;
		}

		// Generate lpc coefficients from autocorr values

		error = aut[0];

		for (i = 0; i < m; i++) {

			double r = -aut[i + 1];

			if (error == 0) {
				lpci = new float[lpci.length];
				return 0;
			}

			// Sum up this iteration's reflection coefficient; note that in
			// Vorbis
			// we don't save it. If anyone wants to recycle this code and needs
			// reflection coefficients, save the results of 'r' from each
			// iteration.

			for (j = 0; j < i; j++)
				r -= lpc[j] * aut[i - j];
			r /= error;

			// Update LPC coefficients and total error

			lpc[i] = r;
			for (j = 0; j < i / 2; j++) {

				double tmp = lpc[j];

				lpc[j] += r * lpc[i - 1 - j];
				lpc[i - 1 - j] += r * tmp;
			}
			if (i % 2 > 0)
				lpc[j] += lpc[j] * r;

			error *= 1.f - r * r;
		}

		for (j = 0; j < m; j++)
			lpci[j] = (float) lpc[j];

		// we need the error value to know how big an impulse to hit the filter
		// with later

		return new Double(error).floatValue();
	}

	public void vorbis_lpc_predict(float[] coeff, float[] prime, int offset1, int m, int offset2, int n) {

		// in: coeff[0...m-1] LPC coefficients
		// prime[0...m-1] initial values (allocated size of n+m-1)
		// out: data[0...n-1] data samples

		int i, j, o, p;
		float y;
		float[] work = new float[m + n];

		if (prime == null)
			for (i = 0; i < m; i++)
				work[i] = 0.f;
		else
			for (i = 0; i < m; i++)
				work[i] = prime[offset1 + i];

		for (i = 0; i < n; i++) {
			y = 0;
			o = i;
			p = m;
			for (j = 0; j < m; j++)
				y -= work[o++] * coeff[--p];

			prime[offset2 + i] = work[o] = y;
		}
	}

	public int _ve_envelope_search() {

		codec_setup_info ci = vi.codec_setup;
		vorbis_info_psy_global gi = ci.psy_g_param;
		envelope_lookup ve = backend_state.ve;
		int i, j;

		int first = ve.current / ve.searchstep;
		int last = pcm_current / ve.searchstep - VE_WIN;
		if (first < 0)
			first = 0;

		// make sure we have enough storage to match the PCM
		if (last + VE_WIN + VE_POST > ve.storage) {
			ve.storage = last + VE_WIN + VE_POST; // be sure

			// ve.mark = _ogg_realloc(ve.mark,ve.storage*sizeof(*ve.mark));
			int[] temp = new int[ve.storage];
			System.arraycopy(ve.mark, 0, temp, 0, ve.mark.length);
			ve.mark = temp;
		}

		for (j = first; j < last; j++) {

			int ret = 0;

			ve.stretch++;
			if (ve.stretch > VE_MAXSTRETCH * 2)
				ve.stretch = VE_MAXSTRETCH * 2;

			for (i = 0; i < ve.ch; i++) {
				// float *pcm = pcm[i] + ve.searchstep*(j);
				// ret |= _ve_amp( ve, gi, _pcm, ve.band, ve.filter+i*VE_BANDS,
				// j );
				ret |= ve._ve_amp(gi, pcm[i], ve.searchstep * (j), ve.band, ve.filter, i * VE_BANDS, j);
			}

			ve.mark[j + VE_POST] = 0;
			if ((ret & 1) > 0) {
				ve.mark[j] = 1;
				ve.mark[j + 1] = 1;
			}

			if ((ret & 2) > 0) {
				ve.mark[j] = 1;
				if (j > 0)
					ve.mark[j - 1] = 1;
			}

			if ((ret & 4) > 0)
				ve.stretch = -1;
		}

		// ve.out( "", null );

		ve.current = last * ve.searchstep;

		// OOP local variable rename
		int centerW_local = centerW;
		int testW = centerW_local + ci.blocksizes[W] / 4 + ci.blocksizes[1] / 2 + ci.blocksizes[0] / 4;

		j = ve.cursor;

		while (j < ve.current - (ve.searchstep)) { // account for postecho
													// working back one window

			if (j >= testW)
				return 1;

			ve.cursor = j;

			if (ve.mark[j / ve.searchstep] > 0) {

				if (j > centerW_local) {
					ve.curmark = j;
					if (j >= testW)
						return 1;
					return 0;
				}
			}

			j += ve.searchstep;
		}

		return -1;
	}

	public boolean _ve_envelope_mark() {

		envelope_lookup ve = backend_state.ve;
		codec_setup_info ci = vi.codec_setup;

		// local OOP variable rename centerW
		int centerW_local = centerW;
		int beginW = centerW_local - ci.blocksizes[W] / 4;
		int endW = centerW_local + ci.blocksizes[W] / 4;

		if (W > 0) {
			beginW -= ci.blocksizes[lW] / 4;
			endW += ci.blocksizes[nW] / 4;
		} else {
			beginW -= ci.blocksizes[0] / 4;
			endW += ci.blocksizes[0] / 4;
		}

		if (ve.curmark >= beginW && ve.curmark < endW)
			return true;
		{
			int first = beginW / ve.searchstep;
			int last = endW / ve.searchstep;
			int i;
			for (i = first; i < last; i++)
				if (ve.mark[i] > 0)
					return true;
		}

		return false;
	}

	public float _vp_ampmax_decay(float amp) {

		codec_setup_info ci = vi.codec_setup;
		vorbis_info_psy_global gi = ci.psy_g_param;

		int n = ci.blocksizes[W] / 2;
		float secs = (float) n / vi.rate;

		amp += secs * gi.ampmax_att_per_sec;
		if (amp < -9999)
			amp = -9999;

		return amp;
	}
}